package mx.com.crowdgine.display.animation.mocap;

import java.util.ArrayList;
import java.util.List;

public class MocapTextureEncoder {
	public static final int ROOT_DATA_SIZE = 20;
	public static final int FRAME_BONE_ID_SIZE = 4;
	public static final int FRAME_BONE_SIZE = 12;
	public static final int FRAME_ROOT_SIZE = 8;
	public static final int ANIMATION_HEADER_SIZE = 4;
	public static final int BONE_DEFINITION_LIMITS_OFFSET = 28;
	public static final int BONE_DEFINITION_DOF_OFFSET = 20;
	public static final int BONE_DEFINITION_INIT_ROT_ORDER_OFFSET = 16;
	public static final int BONE_DEFINITION_AXIS_ORIENTATION_Z_OFFSET = 14;
	public static final int BONE_DEFINITION_AXIS_ORIENTATION_Y_OFFSET = 13;
	public static final int BONE_DEFINITION_AXIS_ORIENTATION_X_OFFSET = 12;
	public static final int BONE_DEFINITION_BONE_LENGTH_OFFSET = 8;
	public static final int BONE_DEFINITION_DIRECTION_Z_OFFSET = 6;
	public static final int BONE_DEFINITION_DIRECTION_Y_OFFSET = 5;
	public static final int BONE_DEFINITION_DIRECTION_X_OFFSET = 4;
	public static final int BONE_DEFINITION_PARENT_ID_OFFSET = 1;
	public static final int BONE_DEFINITION_ID_OFFSET = 0;
	public static final int NUM_BONES_SECTION_SIZE = 4;
	public static final int ROOT_ORIENTATION_OFFSET = 16;
	public static final int ROOT_POSITION_OFFSET = 12;
	public static final int ROOT_AXIS_OFFSET = 8;
	
	private ASFFile asfFile;
	private List<AMCFile> amcFiles = new ArrayList<AMCFile>();
	
	public ASFFile getAsfFile() {
		return asfFile;
	}
	
	public void setAsfFile(ASFFile asfFile) {
		this.asfFile = asfFile;
	}
	
	public void addAmcFile(AMCFile amcFile) {
		this.amcFiles.add(amcFile);
	}
	
	
	public float [] encodeRootData() {
		float [] data = new float[ROOT_DATA_SIZE];
		
		encodeRootOrders(data);
		encodeRootAxis(data, ROOT_AXIS_OFFSET);
		encodeRootPosition(data, ROOT_POSITION_OFFSET);
		encodeRootOrientation(data, ROOT_ORIENTATION_OFFSET);
		
		return data;
		
	}
	
	
	public float [] encodeBoneData() {
		float [] data = new float[getBoneDataSize()];
		
		encodeNumberOfBones(data, 0);
		encodeBoneDefinitions(data, NUM_BONES_SECTION_SIZE);
		
		return data;
		
	}
	
	
	public float [] encodeAnimationData() {
		float [] data = new float[getAnimationSize()];
		
		encodeAnimation(data, 0, 0);
		
		return data;
	}
	
	
	private int getAnimationSize() {
		return getNumFramesSize() + getNumFrames() * getFrameSize();
	}
	
	
	public int getNumFrames() {
		return this.amcFiles.get(0).getAmcFrames().size();
	}
	
	
	private int getNumFramesSize() {
		return 4;
	}
	
	
	
	private int getNumBones() {
		return this.asfFile.getBones().size();
	}
	
	
	private int getBoneDataSize() {
		return NUM_BONES_SECTION_SIZE + getNumBones() * getBoneDefinitionSize();
	}
	
	
	public float [] encode() {
		
		
		float[] data = allocateData();
		
		encodeRootOrders(data);
		encodeRootAxis(data, ROOT_AXIS_OFFSET);
		encodeRootPosition(data, ROOT_POSITION_OFFSET);
		encodeRootOrientation(data, ROOT_ORIENTATION_OFFSET);
		encodeNumberOfBones(data, getMaxRowSize());
		encodeBoneDefinitions(data, getMaxRowSize()+NUM_BONES_SECTION_SIZE);
		encodeNumAnimations(data, getMaxRowSize()*2);
		encodeAnimations(data);
		
		return data;
		
	}

	private void encodeAnimations(float[] data) {
		for(int i = 0; i < amcFiles.size(); i++) {
			encodeAnimation(data, getMaxRowSize()*(i+3), i);
		}
	}
	
	
	public float [] encodeFrame(int frameIndex, int offset, int animationIndex) {
		float [] data = new float[getFrameSize()];

		List<AMCFrame> amcFrames = this.amcFiles.get(animationIndex).getAmcFrames();
		
		data[offset + 0] =  amcFrames.get(frameIndex).getAmcBones().get(0).getValues()[0];
		data[offset + 1] =  amcFrames.get(frameIndex).getAmcBones().get(0).getValues()[1];
		data[offset + 2] =  amcFrames.get(frameIndex).getAmcBones().get(0).getValues()[2];
		
		data[offset + 4] =  amcFrames.get(frameIndex).getAmcBones().get(0).getValues()[3];
		data[offset + 5] =  amcFrames.get(frameIndex).getAmcBones().get(0).getValues()[4];
		data[offset + 6] =  amcFrames.get(frameIndex).getAmcBones().get(0).getValues()[5];
		
		int boneSize = getBoneSize();
		
		for(int b = 1; b < amcFrames.get(frameIndex).getAmcBones().size(); b++) {
			data[offset + boneSize * (b - 1) + FRAME_ROOT_SIZE + 0] = this.asfFile.getBone(amcFrames.get(frameIndex).getAmcBones().get(b).getName()).getId();
			
			int v = 0;
			for(float value : amcFrames.get(frameIndex).getAmcBones().get(b).getValues()) {
				data[offset + boneSize * (b - 1) + FRAME_ROOT_SIZE + FRAME_BONE_ID_SIZE + v] = value;
				
				if(v == 2) {
					v++;
				}
				
				v++;
			}
			
		}
		
		return data;
	}
	
	
	private int getBoneSize() {
		return 12;
	}
	
	
	private void encodeAnimation(float [] data, int offset, int i) {
		data[offset] = this.amcFiles.get(i).getAmcFrames().size();
		data[offset+1] = ":DEGREES".equals(this.amcFiles.get(i).getRotationUnits()) ? 1 : 2;
		
		
		
		List<AMCFrame> amcFrames = this.amcFiles.get(i).getAmcFrames();
		
		for(int j = 0; j < amcFrames.size(); j++) {
			// encode root data
			data[offset + ANIMATION_HEADER_SIZE + j*getFrameSize() + 0] =  amcFrames.get(j).getAmcBones().get(0).getValues()[0];
			data[offset + ANIMATION_HEADER_SIZE + j*getFrameSize() + 1] =  amcFrames.get(j).getAmcBones().get(0).getValues()[1];
			data[offset + ANIMATION_HEADER_SIZE + j*getFrameSize() + 2] =  amcFrames.get(j).getAmcBones().get(0).getValues()[2];
			
			data[offset + ANIMATION_HEADER_SIZE + j*getFrameSize() + 4] =  amcFrames.get(j).getAmcBones().get(0).getValues()[3];
			data[offset + ANIMATION_HEADER_SIZE + j*getFrameSize() + 5] =  amcFrames.get(j).getAmcBones().get(0).getValues()[4];
			data[offset + ANIMATION_HEADER_SIZE + j*getFrameSize() + 6] =  amcFrames.get(j).getAmcBones().get(0).getValues()[5];
			
			int boneSize = 12;
			
			for(int b = 1; b < amcFrames.get(j).getAmcBones().size(); b++) {
				data[offset + ANIMATION_HEADER_SIZE + j*getFrameSize() + boneSize * (b - 1) + FRAME_ROOT_SIZE + 0] = this.asfFile.getBone(amcFrames.get(j).getAmcBones().get(b).getName()).getId();
				
				int v = 0;
				for(float value : amcFrames.get(j).getAmcBones().get(b).getValues()) {
					data[offset + ANIMATION_HEADER_SIZE + j*getFrameSize() + boneSize * (b - 1) + FRAME_ROOT_SIZE + FRAME_BONE_ID_SIZE + v] = value;
					
					if(v == 2) {
						v++;
					}
					
					v++;
				}
				
			}
			
			
		}
		
	}

	public int getFrameSize() {
		int frameSize = FRAME_ROOT_SIZE + this.asfFile.getBones().size()*FRAME_BONE_SIZE;
		return frameSize;
	}
	
	
	private void encodeNumAnimations(float [] data, int offset) {
		data[offset] = this.amcFiles.size();
	}
	
	
	private void encodeBoneDefinitions(float [] data, int offset) {
		
		
		for(int i = 0; i < asfFile.getBones().size(); i++) {
			encodeBoneDefinitionIds(data, offset, getBoneDefinitionSize(), i);
			
			encodeBoneDefinitionDirection(data, offset, getBoneDefinitionSize(), i);
			
			encodeBoneDefinitionBoneLength(data, offset, getBoneDefinitionSize(), i);
			
			encodeBoneDefinitionAxisOrientation(data, offset,
					getBoneDefinitionSize(), i);
			
			
			encodeBoneDefinitionInitialRotationOrder(data, offset,
					getBoneDefinitionSize(), i);
			
			
			encodeBoneDefinitionDegreesOfFreedom(data, offset,
					getBoneDefinitionSize(), i);
			
			
			encodeBoneDefinitionLimits(data, offset, getBoneDefinitionSize(), i);
			
		}
	}

	private void encodeBoneDefinitionLimits(float[] data, int offset,
			int boneDefinitionSize, int i) {
		List<ASFBoneLimit> limits = asfFile.getBones().get(i).getLimits();
		
		for(int j = 0; j < limits.size(); j++) {
			data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_LIMITS_OFFSET + 2*j] = limits.get(j).getLowerBound();
			data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_LIMITS_OFFSET + 2*j + 1] = limits.get(j).getUpperBound();
		}
	}

	private void encodeBoneDefinitionDegreesOfFreedom(float[] data, int offset,
			int boneDefinitionSize, int i) {
		String [] degreesOfFreedom = asfFile.getBones().get(i).getDof();
		
		for(int j = 0; degreesOfFreedom != null && j < degreesOfFreedom.length; j++) {
			if("tx".equals(degreesOfFreedom[j])) {
				data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_DOF_OFFSET + j] = 1.0f;
			} else if("ty".equals(degreesOfFreedom[j])) {
				data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_DOF_OFFSET + j] = 2.0f;
			} else if("tz".equals(degreesOfFreedom[j])) {
				data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_DOF_OFFSET + j] = 3.0f;
			} else if("rx".equals(degreesOfFreedom[j])) {
				data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_DOF_OFFSET + j] = 4.0f;
			} else if("ry".equals(degreesOfFreedom[j])) {
				data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_DOF_OFFSET + j] = 5.0f;
			} else if("rz".equals(degreesOfFreedom[j])) {
				data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_DOF_OFFSET + j] = 6.0f;
			} else if("l".equals(degreesOfFreedom[j])) {
				data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_DOF_OFFSET + j] = 7.0f;
			}
		}
	}

	private void encodeBoneDefinitionInitialRotationOrder(float[] data,
			int offset, int boneDefinitionSize, int i) {
		String initialRotationOrder = asfFile.getBones().get(i).getAxisInitialRotationOrder();
		
		for(int j = 0; j < initialRotationOrder.length(); j++) {
			switch(initialRotationOrder.charAt(j)) {
				case 'X':
					data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_INIT_ROT_ORDER_OFFSET + j] = 1;
					break;
				case 'Y':
					data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_INIT_ROT_ORDER_OFFSET + j] = 2;
					break;
				case 'Z':
					data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_INIT_ROT_ORDER_OFFSET + j] = 3;
					break;
			}
		}
	}

	private void encodeBoneDefinitionAxisOrientation(float[] data, int offset,
			int boneDefinitionSize, int i) {
		data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_AXIS_ORIENTATION_X_OFFSET] = asfFile.getBones().get(i).getAxisOrientation()[0];
		data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_AXIS_ORIENTATION_Y_OFFSET] = asfFile.getBones().get(i).getAxisOrientation()[1];
		data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_AXIS_ORIENTATION_Z_OFFSET] = asfFile.getBones().get(i).getAxisOrientation()[2];
		// data[offset + i*boneDefinitionSize + 15] // nothing
	}

	private void encodeBoneDefinitionBoneLength(float[] data, int offset,
			int boneDefinitionSize, int i) {
		data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_BONE_LENGTH_OFFSET] = asfFile.getBones().get(i).getLength();
		// data[offset + i*boneDefinitionSize + 9] // nothing
		// data[offset + i*boneDefinitionSize + 10] // nothing
		// data[offset + i*boneDefinitionSize + 11] // nothing
	}

	private void encodeBoneDefinitionDirection(float[] data, int offset,
			int boneDefinitionSize, int i) {
		data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_DIRECTION_X_OFFSET] = asfFile.getBones().get(i).getDirection()[0];
		data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_DIRECTION_Y_OFFSET] = asfFile.getBones().get(i).getDirection()[1];
		data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_DIRECTION_Z_OFFSET] = asfFile.getBones().get(i).getDirection()[2];
		// data[offset + i*boneDefinitionSize + 7] // nothing
	}

	private void encodeBoneDefinitionIds(float[] data, int offset,
			int boneDefinitionSize, int i) {
		data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_ID_OFFSET] = asfFile.getBones().get(i).getId();
		data[getBoneDefinitionOffset(offset, boneDefinitionSize, i) + BONE_DEFINITION_PARENT_ID_OFFSET] = asfFile.getBones().get(i).getParentId();
		// data[offset + i*boneDefinitionSize + 2] // nothing
		// data[offset + i*boneDefinitionSize + 3] // nothing
	}

	public int getBoneDefinitionOffset(int offset, int boneDefinitionSize, int i) {
		return offset + i*boneDefinitionSize;
	}

	public int getBoneDefinitionSize() {
		int boneDefinitionSize = 44;
		return boneDefinitionSize;
	}
	
	
	private void encodeNumberOfBones(float [] data, int offset) {
		data[offset] = asfFile.getBones().size();
	}
	
	
	
	
	private void encodeRootOrientation(float [] data, int offset) {
		for(int i = 0; i < asfFile.getRootOrientation().length; i++) {
			data[offset + i] = asfFile.getRootOrientation()[i];
		}
	}
	
	
	private void encodeRootPosition(float [] data, int offset) {
		for(int i = 0; i < asfFile.getRootPosition().length; i++) {
			data[offset + i] = asfFile.getRootPosition()[i];
		}
	}
	
	
	
	private void encodeRootAxis(float [] data, int offset) {
		
		for(int i = 0; i < asfFile.getRootAxis().length(); i++) {
			
			switch(asfFile.getRootAxis().charAt(i)) {
				case 'X':
					data[offset + i] = 1.0f;
					break;
				case 'Y':
					data[offset + i] = 2.0f;
					break;
				case 'Z':
					data[offset + i] = 3.0f;
					break;
				default:
					data[offset + i] = 0.0f;
			}
			
		}
		
	}
	
	
	private void encodeRootOrders(float[] data) {
		int i = 0;
		for(String order : asfFile.getRootOrders()) {
			
			if("TX".equals(order)) {
				data[i] = 1.0f;
			} else if("TY".equals(order)) {
				data[i] = 2.0f;
			} else if("TZ".equals(order)) {
				data[i] = 3.0f;
			} else if("RX".equals(order)) {
				data[i] = 4.0f;
			} else if("RY".equals(order)) {
				data[i] = 5.0f;
			} else if("RZ".equals(order)) {
				data[i] = 6.0f;
			} else {
				data[i] = 0.0f;
			}
			
			if(i == 2){
				// skip one
				i++;
			}
			
			i++;
		}
	}
	
	
	private float[] allocateData() {
		int rows = 3 + amcFiles.size(); // root row + bonedata row + num animations row + animation rows
		int maxRowSize = getMaxRowSize();
		
		int dataSize = rows * maxRowSize;
		
		float [] data = new float[dataSize];
		return data;
	}

	public int getMaxRowSize() {
		// assuming animations section has the longest rows
		int numFrames = this.amcFiles.get(0).getAmcFrames().size();
		int numBones = asfFile.getBones().size();
		int boneSize = 12; // id (4 floats) + two RGBs (8 floats)
		int frameSize = 8 + numBones * boneSize; // 8 floats for root + numBones * boneSize
		int maxRowSize = 4 + numFrames*frameSize; // 4 floats for num frames and units of rotation
		return maxRowSize;
	}
	
	
	public int getTextureWidth() {
		return getMaxRowSize()/4;
	}
	
	public int getTextureHeight() {
		return 3 + amcFiles.size();
	}
	
}
